package com.zelin.search.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.zelin.search.entity.ItemEntity;
import com.zelin.search.entity.ItemVo;
import com.zelin.search.service.ItemSearchService;
import org.apache.lucene.queryparser.xml.builders.BooleanQueryBuilder;
import org.apache.lucene.search.BooleanQuery;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.MoreLikeThisQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

/**
 * ------------------------------
 * 功能：
 * 作者：WF
 * 微信：hbxfwf13590332912
 * 创建时间：2021/11/2-16:09
 * ------------------------------
 */
@Service
public class ItemSearchServiceImpl implements ItemSearchService {
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    private ElasticsearchRestTemplate restTemplate;
    //1. 开始进行查询
    @Override
    public Map<String, Object> search(ItemVo itemVo) {
        System.out.println("itemVo = " + itemVo);
        /*------------------------  第一部分：查询部分 ----------------------------*/
        //1.0 定义返回的map集合
        Map<String, Object> map = new HashMap<>();
        //1.1 得到NativeSearchQueryBuilder
        NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder();
        //1.2 得到查询关键字
        String keywords = itemVo.getKeywords();
        if(StrUtil.isBlank(keywords)){
            keywords = "";
        }
        //1.3 进行多字段匹配查询
        builder.withQuery(QueryBuilders.multiMatchQuery(keywords,"title","brand","category"));
        //1.4 进行分类查询（分组查询）
        builder.addAggregation(AggregationBuilders.terms("categoryGroup").field("category.keyword").size(50));
        //1.5 添加高亮查询
        builder.withHighlightBuilder(new HighlightBuilder().field("title")
                .preTags("<span style='color:red'>")
                .postTags("</span>"));
        //1.6 进行过滤查询
        //1.6.1 进行分类查询
        //定义用于组合多个查询条件的BooleanQuery对象
        BoolQueryBuilder booleanQuery = QueryBuilders.boolQuery();
        if(StrUtil.isNotBlank(itemVo.getCategory())) {
            booleanQuery.filter(QueryBuilders.termQuery("category.keyword", itemVo.getCategory()));
        }
        //1.6.2 进行品牌查询
        if(StrUtil.isNotBlank(itemVo.getBrand())){
            booleanQuery.filter(QueryBuilders.termQuery("brand.keyword",itemVo.getBrand()));
        }
        //1.6.3 进行规格查询
        if(itemVo.getSpec() != null && itemVo.getSpec().size() > 0){
            //① 得到规格的内容
            List<String> spec = itemVo.getSpec();
            //② 遍历集合，添加过滤查询条件
            for (String s : spec) {
                //1. 进行拆分，得到key（字段名--》specMap.key）与value：
                String[] split = s.split(":");
                //2. 添加过滤查询条件
                booleanQuery.filter(QueryBuilders.termQuery("specMap." + split[0] + ".keyword",split[1]));
            }
        }
        //1.6.4 进行价格区间查询
        if(StrUtil.isNotBlank(itemVo.getPrice())){
            //① 得到价格区间
            String price = itemVo.getPrice();
            //② 进行拆分，得到开始区间与结束区间
            String[] split = price.split("-");
            //③ 添加过滤条件
            if(split[1].equals("*")){
                booleanQuery.filter(QueryBuilders.rangeQuery("price").gte(split[0]));
            }else{
                booleanQuery.filter(QueryBuilders.rangeQuery("price").gt(split[0]).lte(split[1]));
            }
        }
        //1.6.5 进行排序查询
        if(StrUtil.isNotBlank(itemVo.getSort())){
            //① 得到排序字符串
            String sort = itemVo.getSort();
            //② 进行拆分，得到排序字段及排序关键字
            String[] split = sort.split(":");
            //③ 开始排序
            builder.withSort(SortBuilders.fieldSort(split[0]).order(split[1].equals("asc") ? SortOrder.ASC : SortOrder.DESC));
        }
        builder.withFilter(booleanQuery);

        //1.6.6 进行分页查询
        int page = itemVo.getPage();
        int pageSize = itemVo.getPageSize();
        builder.withPageable(PageRequest.of(page-1,pageSize));


        Query query = builder.build();


        /*------------------------  第二部分：对查询结果进行分析 ----------------------------*/
        //2.1 得到查询的命中对象
        SearchHits<ItemEntity> searchHits = restTemplate.search(query, ItemEntity.class, IndexCoordinates.of("item"));
        //2.2 得到真实的命中数据
        long total = searchHits.getTotalHits();                     //总记录数
        //计算总页数
        int totalpage = (int)Math.ceil(total/(double)pageSize);

        //2.3 定义存放高亮数据的集合
        List<ItemEntity> highlights = new ArrayList<>();
        List<SearchHit<ItemEntity>> hits = searchHits.getSearchHits();  //得到搜索的命中对象
        for (SearchHit<ItemEntity> hit : hits) {
            //2.3.1 得到高亮数据
            List<String> title = hit.getHighlightField("title");
            //2.3.2 得到原始未加高亮前的数据，并得新设置其标题（title）
            //① 得到高亮前的数据
            ItemEntity content = hit.getContent();
            //② 重新设置标题（带有高亮）
            if(title != null && title.size() > 0){
                content.setTitle(title.get(0));
                //2.3.3 添加到高亮集合中
                highlights.add(content);
            }
        }
        System.out.println("【高亮数据】：" + highlights);

        Aggregations aggregations = searchHits.getAggregations();
        //取得分组名为categoryGroup的数据
        ParsedStringTerms aggregationGroup = aggregations.get("categoryGroup");
        List<? extends Terms.Bucket> buckets = aggregationGroup.getBuckets();
        System.out.println("-------------------------");
        for (Terms.Bucket bucket : buckets) {
            String string = bucket.getKeyAsString();
            System.out.println(string);
        }
        System.out.println("-------------------------");
        //查询出分类列表
        List<String> categoryList = aggregationGroup.getBuckets().stream()
                .map(m -> m.getKeyAsString()).collect(Collectors.toList());

        //1.3 得到分类参数
        String category = itemVo.getCategory();
        Map brandAndSpecList = new HashMap();
        if(StrUtil.isBlank(category)){
            if(CollectionUtil.isNotEmpty(categoryList)){
                category = categoryList.get(0);
                //1.4 根据上面的分类在redis中查询出品牌列表及规格列表
                brandAndSpecList = findBrandAndSpecList(category);
            }else{
                category = "";
                brandAndSpecList.put("brandList",new ArrayList<>());
                brandAndSpecList.put("specList",new ArrayList<>());
            }

        }

        //1.5 将得到的map放到结果的大map中
        map.putAll(brandAndSpecList);
        //1.6 将得到的分类名称集合放到map中
        map.put("categoryList",categoryList);
        //1.7 将高亮查询数据放到结果的map集合中
        map.put("rows",highlights);
        //1.8 添加分页参数
        map.put("total",total);
        map.put("totalPage",totalpage);
        return map;
    }

    //2. 根据分类名称在redis中查询出品牌及规格列表
    private Map findBrandAndSpecList(String category) {
        //2.1 根据分类名称查询出模板id
        Long typeId = Long.parseLong(redisTemplate.boundHashOps("itemCats").get(category) + "");
        //2.2 根据模板id查询出品牌及规格列表
        String brandStr = (String) redisTemplate.boundHashOps("brandList").get(typeId+"");
        String specStr = (String) redisTemplate.boundHashOps("specList").get(typeId+"");
        //2.3 转换为List集合
        List<Map> brandList = JSON.parseArray(brandStr, Map.class);
        List<Map> specList = JSON.parseArray(specStr, Map.class);
        //2.4 定义结果map
        Map map = new HashMap();
        map.put("brandList",brandList);
        map.put("specList",specList);
        //2.5 返回
        return map;
    }
}
